
#include <BRender>
#include <BMaterial>
#include <BScene>
#include "member_BCamera.h"
#include "member_BGroup.h"
#include "member_BNode.h"
#include "member_BShape.h"
#include "member_BHandler.h"
#include "member_BScene.h"
#include "member_BRender.h"

using namespace BWE;

member_BCamera::member_BCamera(BCamera* camera)
{
	boss = camera;
	ortho = false;
	up.set(0, 0, 1);
	fovy = 36;
	aspect = 1;
	near = 1;
	far = 100;
	lensWidth = 1;
	lensHeight = 1;
	refresh = false;
	culling = true;
	scene = 0;
	pixelSizePolicy = Policy_Dynamic;

	dirty = true;
	fbo_resized = true;
	fbo = 0;
	rbo_depth = 0;
	tbo = 0;

}
member_BCamera::~member_BCamera()
{

}

void member_BCamera::adjust()
{
	if (!ortho && fovy > 0.0)
	{
		BReal hfovy = fovy * 0.5 / 180.0 * PI;
		lensHeight = near * tan(hfovy) * 2.0;
		lensWidth = lensHeight * aspect;
	}
}

void member_BCamera::freshFrustum()
{
	const BVector& pos = boss->position();
	direct = center - pos;
	direct.normalize();

	BVector direct_up = up;

	BVector direct_right = direct.cross(up);
	direct_right.normalize();

	direct_up = direct_right.cross(direct);
	direct_up.normalize();

	direct_right *= (BReal)(lensWidth * 0.5);
	direct_up *= (BReal)(lensHeight * 0.5);

	BVector near_center = pos + direct * near;
	BVector near_v0 = near_center - direct_right + direct_up;
	BVector near_v1 = near_center - direct_right - direct_up;
	BVector near_v2 = near_center + direct_right - direct_up;
	BVector near_v3 = near_center + direct_right + direct_up;

	frustum.near_v0 = near_v0;
	frustum.near_v1 = near_v1;
	frustum.near_v2 = near_v2;
	frustum.near_v3 = near_v3;

	if (ortho)
	{
		BReal depth = far - near;
		frustum.far_v0 = near_v0 + direct * depth;
		frustum.far_v1 = near_v1 + direct * depth;
		frustum.far_v2 = near_v2 + direct * depth;
		frustum.far_v3 = near_v3 + direct * depth;

		frustum.plane_near.set(direct, pos + direct * near);
		frustum.plane_far.set(-direct, pos + direct * far);
		frustum.plane_left.set(direct_right, near_v0);
		frustum.plane_right.set(-direct_right, near_v3);
		frustum.plane_top.set(-direct_up, near_v0);
		frustum.plane_bottom.set(direct_up, near_v1);
	}
	else
	{
		BReal ratio = far / near;
		frustum.far_v0 = pos + (near_v0 - pos) * ratio;
		frustum.far_v1 = pos + (near_v1 - pos) * ratio;
		frustum.far_v2 = pos + (near_v2 - pos) * ratio;
		frustum.far_v3 = pos + (near_v3 - pos) * ratio;

		frustum.plane_near.set(direct, pos + direct * near);
		frustum.plane_far.set(-direct, pos + direct * far);
		frustum.plane_left.set(near_v0, pos, near_v1);
		frustum.plane_right.set(near_v2, pos, near_v3);
		frustum.plane_top.set(near_v3, pos, near_v0);
		frustum.plane_bottom.set(near_v1, pos, near_v2);
	}

}

void member_BCamera::prepare()
{
	shapeArray.reset();
	transShapeQueue.reset();
	if (scene_member(scene)->sceneOctTree)
	{
		sceneItems.reset();
		scene_member(scene)->sceneOctTree->intersect(frustum.space(), sceneItems);
		for (int i = 0; i < sceneItems.size(); i++)
		{
			SceneOctTreeItem& item = sceneItems[i];
			prepare(item);
		}
	}
	else
	{
		sceneItems.clear();
		BSet<BNodeHolder>& nodes = scene_member(scene)->nodes;
		BSet<BShapeHolder>& shapes = scene_member(scene)->shapes;
		prepareShapes(nodes, shapes);
		BSet<BGroupHolder>& groups = scene_member(scene)->groups;
		for (int i = 0; i < groups.size(); i++)
		{
			prepare(groups[i]);
		}
	}
}
void member_BCamera::prepare(SceneOctTreeItem& item)
{
	BArray<BNodeHolder>& nodes = item->nodes;
	for (int i = 0; i < nodes.size(); i++)
	{
		BNode* node = nodes[i];
		if (node->visible() == false)
			continue;

		const BMatrix& nodeMatrix = node->matrix();
		BBox box = node->box() * nodeMatrix;
		if (frustum.clip(box))
			continue;

		for (int i = 0; i < node->shapeCount(); i++)
		{
			BShape* shape = node->shape(i);
			if (!shape->visible())
				continue;

			BMatrix matrix = shape->matrix() * nodeMatrix;
			BBox box = shape->box() * matrix;
			if (frustum.clip(box))
				continue;

			ShapeInfo sinfo;
			sinfo.shape = shape;
			sinfo.node = node;
			sinfo.box = box;
			if (shape_member(shape)->transparent)
			{
				BReal dist = frustum.plane_near.dist(box.center());
				transShapeQueue.insert(dist, sinfo);
			}
			else
			{
				shapeArray.append(sinfo);
			}
		}
	}
	BArray<BShapeHolder>& shapes = item->shapes;
	for (int i = 0; i < shapes.size(); i++)
	{
		BShape* shape = shapes[i];
		if (!shape->visible())
			continue;

		BMatrix matrix = shape->matrix();
		BBox box = shape->box() * matrix;
		if (frustum.clip(box))
			continue;

		ShapeInfo sinfo;
		sinfo.shape = shape;
		sinfo.box = box;
		if (shape_member(shape)->transparent)
		{
			BReal dist = frustum.plane_near.dist(box.center());
			transShapeQueue.insert(dist, sinfo);
		}
		else
		{
			shapeArray.append(sinfo);
		}
	}
}
void member_BCamera::prepare(BGroup* group)
{
	if (group_member(group)->visible)
	{
		BSet<BNodeHolder>& nodes = group_member(group)->nodes;
		BSet<BShapeHolder>& shapes = group_member(group)->shapes;
		prepareShapes(nodes, shapes);
		BArray<BGroupHolder>& children = group_member(group)->children;
		for (int i = 0; i < children.size(); i++)
		{
			prepare(children[i]);
		}
	}
}
void member_BCamera::prepareShapes(BSet<BNodeHolder>& nodes, BSet<BShapeHolder>& shapes)
{
	for (int i = 0; i < nodes.size(); i++)
	{
		BNode* node = nodes[i];
		if (node->visible() == false)
			continue;

		const BMatrix& nodeMatrix = node->matrix();
		BBox box = node->box() * nodeMatrix;
		if (frustum.clip(box))
			continue;

		for (int i = 0; i < node->shapeCount(); i++)
		{
			BShape* shape = node->shape(i);
			if (!shape->visible())
				continue;

			BMatrix matrix = shape->matrix() * nodeMatrix;
			BBox box = shape->box() * matrix;
			if (frustum.clip(box))
				continue;

			ShapeInfo sinfo;
			sinfo.shape = shape;
			sinfo.node = node;
			sinfo.box = box;
			if (shape_member(shape)->transparent)
			{
				BReal dist = frustum.plane_near.dist(box.center());
				transShapeQueue.insert(dist, sinfo);
			}
			else
			{
				shapeArray.append(sinfo);
			}
		}
	}
	for (int i = 0; i < shapes.size(); i++)
	{
		BShape* shape = shapes[i];
		if (!shape->visible())
			continue;

		BMatrix matrix = shape->matrix();
		BBox box = shape->box() * matrix;
		if (frustum.clip(box))
			continue;

		ShapeInfo sinfo;
		sinfo.shape = shape;
		sinfo.box = box;
		if (shape_member(shape)->transparent)
		{
			BReal dist = frustum.plane_near.dist(box.center());
			transShapeQueue.insert(dist, sinfo);
		}
		else
		{
			shapeArray.append(sinfo);
		}
	}
}

inline void DrawShape(ShapeInfo& sinfo, BRender* render)
{
	BShape* shape = sinfo.shape;

	render_member(render)->node = sinfo.node;
	render_member(render)->shape = sinfo.shape;

	glPushMatrix();
	glPushAttrib(GL_ALL_ATTRIB_BITS);

	render->setLighting(shape_member(shape)->lighting);
	render->setDepthTest(shape_member(shape)->depthTest);
	render->setCullback(shape_member(shape)->cullback);
	render->setLineSmooth(shape_member(shape)->lineSmooth);
	render->setPointSmooth(shape_member(shape)->pointSmooth);
	render->setLineWidth(shape_member(shape)->lineWidth);
	render->setPointSize(shape_member(shape)->pointSize);
	render->setPolyMode(shape_member(shape)->polyMode);

	if (render_member(render)->lighting && render_member(render)->lights)
	{
		render_member(render)->useLights(sinfo.box.center());
		const BMaterial* material = shape_member(shape)->material;
		if (material && material->enabled())
		{
			render->setMaterial(material);
			glDisable(GL_COLOR_MATERIAL);
		}
		else
		{
			glEnable(GL_COLOR_MATERIAL);
			render->setColor(shape_member(shape)->color);
		}
	}
	else
	{
		glDisable(GL_COLOR_MATERIAL);
		render->setColor(shape_member(shape)->color);
	}

	if (sinfo.node)
	{
		const BMatrix& matrix = sinfo.node->matrix();
		BOpenGL::glMultMatrix(matrix);

		BVector scale = matrix.scale();
		BOpenGL::glScale(scale.x(), scale.y(), scale.z());
	}

	const BMatrix& matrix = shape_member(shape)->matrix;
	BOpenGL::glMultMatrix(matrix);

	BVector scale = matrix.scale();
	BOpenGL::glScale(scale.x(), scale.y(), scale.z());

	const BProgram* program = shape_member(shape)->program;
	if (program)
		render->setProgram(program);

	if (shape_member(shape)->textures.size())
	{
		BArray<BTextureHolder>& textures = shape_member(shape)->textures;
		for (int i = 0; i < textures.size(); i++)
		{
			BTexture* texture = textures[i];
			render->setTexture(texture, i);
		}
	}

	shape_member(shape)->render(*render);

	if (program)
		render->setProgram(0);

	if (int texcount = shape_member(shape)->textures.size())
	{
		for (int i = 0; i < texcount; i++)
		{
			render->setTexture(0, i);
		}
	}

	render_member(render)->node = 0;
	render_member(render)->shape = 0;

	glPopAttrib();
	glPopMatrix();
}

void member_BCamera::frame(BScene* scene, BHandler* handler)
{
	this->scene = scene;
	while (refresh)
	{
		refresh = false;
		int width = pixelSize.width();
		int height = pixelSize.height();
		if (fbo == 0 || rbo_depth == 0 || tbo == 0)
			fbo_resized = true;
		if (fbo_resized)
		{
			GLint backup_frame_buffer;
			glGetIntegerv(GL_FRAMEBUFFER_BINDING, &backup_frame_buffer);

			if (!fbo)
			{
				glGenFramebuffers(1, &fbo);
				glGenRenderbuffers(1, &rbo_depth);
			}
			if (fbo)
			{
				glBindFramebuffer(GL_FRAMEBUFFER, fbo);
				glBindRenderbuffer(GL_RENDERBUFFER, rbo_depth);
				glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height);
				glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo_depth);
			}
			if (!tbo)
			{
				glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
				glGenTextures(1, &tbo);
			}
			if (tbo)
			{
				glBindTexture(GL_TEXTURE_2D, tbo);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
				glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, 0);
				glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, tbo, 0);
			}
			fbo_resized = false;

			glBindFramebuffer(GL_FRAMEBUFFER, backup_frame_buffer);
		}
		if (fbo)
		{
			freshFrustum();
			prepare();

			BRender render(boss);
			render_member(&render)->lights = &scene_member(scene)->lights;

			glClearDepth(1);
			glClear(GL_DEPTH_BUFFER_BIT);

			glClearColor(color.r() / 255.0f, color.g() / 255.0f, color.b() / 255.0f, color.a() / 255.0f);
			glClear(GL_COLOR_BUFFER_BIT);

			//GLfloat fogColor[4] = { 0.5f, 0.5f, 0.5f, 0.5f };
			//glEnable(GL_FOG);
			//glFogi(GL_FOG_MODE, GL_LINEAR);
			//glFogfv(GL_FOG_COLOR, fogColor);
			//glFogf(GL_FOG_DENSITY, 0.6f);
			//glHint(GL_FOG_HINT, GL_NICEST);
			//glFogf(GL_FOG_START, near);
			//glFogf(GL_FOG_END, far);
			for (int i = 0; i < shapeArray.size(); i++)
			{
				ShapeInfo& sinfo = shapeArray[i];
				DrawShape(sinfo, &render);
			}
			for (auto it = transShapeQueue.rbegin(); it != transShapeQueue.rend(); ++it)
			{
				ShapeInfo& sinfo = *it;
				DrawShape(sinfo, &render);
			}
			renderHandler(handler, render);
			boss->emit(Signal_Frame);
		}
	}
	this->scene = 0;
}

void member_BCamera::renderHandler(BHandler* handler, BRender& render)
{
	if (!handler || !handler->enabled())
		return;
	glPushMatrix();
	glPushAttrib(GL_ALL_ATTRIB_BITS);
	handler_member(handler)->render(render);
	glPopAttrib();
	glPopMatrix();
	for (int i = 0; i < handler_member(handler)->children.size(); i++)
	{
		BHandler* child = handler_member(handler)->children[i];
		renderHandler(child, render);
	}
}

